//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
//
//	File		 :	ResidueGroup.cpp
//	Contents	 :	Source file for class CResidueGroupInfo
//					Reads and write configuration information including groups of
//						residues in	binary and text.
//	Creation Date:	27/12/1999
//	Last Modified:	2/6/20103	lg, Y2K bug shows that these lines are machine generated
//	tabs width	 :	every 4 characters
//
//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

#include <cassert>
#include <algorithm>
#include <string>
#include <iostream>
#include <ostream>
#include <istream>
#include <sstream>
#include "binarystream.h"
#include "msfresource.h"
#include "../components/perlutil.h"
#ifdef _MFC_VER	// mfc specific code to follow
#include "../common/mfc_stdcpp.h"
#else
#include "../common/nonmfc_stdcpp.h"
#endif
#include "residuegroup.h"

using std::istream;
using std::cerr;
using std::istringstream;
using std::vector;
using std::string;
using std::ostream;
using std::unique;
using std::sort;



// anonymous namespace for functions only used in this file
namespace{

//_______________________________________________________________________________________
//
//	ReadBoolFromText
//
//		First modified: lg 28/12/1999
//_______________________________________________________________________________________
bool ReadBoolFromText(bool& b, const string& token)
{
	// get next token
	// end should point to terminating null if succeeded
	char* end;
	b = (strtoul(token.c_str(), &end, 10) !=0);
	if (!*end)
	{
		return true;
	}
	
	return false;
}


//_______________________________________________________________________________________
//
//	ReadRGBFromText
//
//		Return false if fail
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
bool ReadRGBFromText(CColourref& col, const string& token)
{
	istringstream istrm("," + token);
	unsigned rgb[3];
	char separator;
	
	for (int i = 0; i < 3; ++i)
	{
		istrm >> separator >> rgb[i];
	}
	if (istrm.fail())
		return false;
	col.l = ((0x000000FF & rgb[0]) << 16 )	|
			((0x000000FF & rgb[1]) << 8	)	|
			((0x000000FF & rgb[2])	)	;
	
	return true;
}

} // anonymous namespace


//_______________________________________________________________________________________
//
//	ReadInitialData
//
//		Reads the first data line in the configuration file which contains the
//			font name and sizes
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
bool CResidueGroupsInfo::ReadInitialData(istream& is)
{
	ErrorLineNumber = 0;
//	char seps[]   = " ;,\t\r";

	// skip past comment and blank lines to the first line with data
	string line;
	while (getline(is, line)) // 1-based line number
	{
		// increment current line number
		++ErrorLineNumber;

		// skip blank or comment lines
		if (line.size() < 2)
			continue;
		if (line[0] == '/' && line[1] == '/')
			continue;

		istringstream sstrm(line);
		
		// set text size
		// end should point to terminating null if succeeded
		sstrm >> TextSizeInTenths;
		getline(sstrm, TextFont);
		
		if (sstrm.fail())
		{
			//"The first line of data did not contain a valid font size and name"
			LoadString(ErrorStr, IDS_ERR_RESGRP_FONTSZ);
			return false;
		}

		return true;
	}

	// "No configuration data could be found."
	LoadString(ErrorStr, IDS_ERR_RESGRP_NODATA);
	return false;
}

//_______________________________________________________________________________________
//
//	ReadResidueDataFromText
//
//		Reads the first data line in the configuration file which contains the
//			font name and sizes
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
bool CResidueGroupsInfo::ReadResidueData(istream& is)
{
	string delimiters(";\t\r");
	CResidueGroupInfo tmp;
	string line;
	while (getline(is,  line))
	{
		// increment current line number
		++ErrorLineNumber;

		// skip blank or comment lines
		if (line.size() < 2)
			continue;
		if (line[0] == '/' && line[1] == '/')
			continue;

		vector<string> data;
		PerlSplit (	line, delimiters, data, false);

		// Get residue group name
		tmp.Name = data[0];

		// Get Residues and make sure A-Z and unique
		if (data.size() < 2)
		{
			//"No residue group data was found after the name."
			LoadString(ErrorStr, IDS_ERR_RESGRP_INCOMPLETEDATA);
			ErrorStr += "\n[" + line + "]";
			return false;
		}

		tmp.Residues = data[1];
		
		for (unsigned i = 0; i < tmp.Residues.length(); ++i)
			if (!isupper(tmp.Residues[i]))
			{
				//"All of the specified residues in a group must be upper case letters.";
				LoadString(ErrorStr, IDS_ERR_RESGRP_RESNOTUPPERCASE);
				return false;
			}
		sort(tmp.Residues.begin(), tmp.Residues.end());
		if (unique(tmp.Residues.begin(), tmp.Residues.end()) != tmp.Residues.end())
		{
			//"All of the residues specified in a group must be unique.";
			LoadString(ErrorStr, IDS_ERR_RESGRP_DUPLICATERESIDUES);
			ErrorStr += "\n[" + line + "]";
			return false;
		}

		
		// get text and background colours
		if ((data.size() < 8)								 ||
			!ReadRGBFromText(tmp.Format.TextColour, data[2]) ||
			!ReadRGBFromText(tmp.Format.BackColour, data[3]) ||

		// get bold and italic
			!ReadBoolFromText(tmp.Format.Bold, data[4])		||
			!ReadBoolFromText(tmp.Format.Italic, data[5])	||
			!ReadBoolFromText(tmp.Active, data[6]))
		{
			//"The residue group consensus font format was not properly specified.";
			LoadString(ErrorStr, IDS_ERR_RESGRP_FONTFORMAT);
			ErrorStr += "\n[" + line + "]";
			return false;
		}
		// first character is marker
		if (data[7].length())
			tmp.Marker = data[7][0];
	

		// default
		if (ResGrps.size() == 0)
		{
			if (stricmp( tmp.Name, "Default")  != 0)
			{
				//"The first font format should be default text and labelled as ""Default""."
				LoadString(ErrorStr, IDS_ERR_RESGRP_DEFAULTFORMAT);
				return false;
			}
			assert(tmp.Residues == "X");
			tmp.Residues = "X";
			// default is always active
			tmp.Active = true;
		}

		// consensus on single residues
		else if (ResGrps.size() == 1)
		{
			if (stricmp( tmp.Name, "Single")  != 0)
			{
				//"The second font format should be for consensus on single residues and labelled as ""Single""."
				LoadString(ErrorStr, IDS_ERR_RESGRP_SINGLEFORMAT);
				return false;
			}
			assert(tmp.Residues == "X");
			tmp.Residues = "X";
		}
		// add residue group to the list
		ResGrps.push_back(tmp);
	}

	// no errors
	ErrorStr = "";
	ErrorLineNumber=0;
	return true;
}

//_______________________________________________________________________________________
//
//	Read
//
//		Streams in the configuration information from text
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
bool CResidueGroupsInfo::Read(istream& is)
{
	if (!ReadInitialData(is) ||
		!ReadResidueData(is))
	{
		ResGrps.clear();
		TextFont = "";
		TextSizeInTenths = 0;
		return false;
	}
	return true;
}




//_______________________________________________________________________________________
//
//	Write
//
//		Private function
//		Streams out the configuration information in text format
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
void CResidueGroupsInfo::Write(ostream& os) const
{
	// write preamble in two parts because text is too long for VC 6 resource editor :->
	string str;
	LoadString(str, IDS_CONFIGFILEPREAMBLE1);
	os << str.c_str();
	LoadString(str, IDS_CONFIGFILEPREAMBLE2);
	os << str.c_str();
	
	// write first line: fonts size and name
	os	<< std::dec << TextSizeInTenths << ";" << TextFont << std::endl;

	// write data lines
	for (unsigned i = 0; i < ResGrps.size(); ++i)
		os	<< std::dec
			<< ResGrps[i].Name
			<< ";"
			<< ResGrps[i].Residues
			<< ";"
			<< CColourref(ResGrps[i].Format.TextColour)
			<< CColourref(ResGrps[i].Format.BackColour)
			<< (ResGrps[i].Format.Bold ? 1: 0)
			<< ";"
			<< (ResGrps[i].Format.Italic ? 1: 0)
			<< ";"
			<< (ResGrps[i].Active ? 1: 0)
			<< ";"
			<< ResGrps[i].Marker
			<< std::endl;
}





//_______________________________________________________________________________________
//
//	GetResidueGrpsFormats
//
//		copies font formats and residue groups into the supplied arrays
//		Returns true if Single Matches are desired
//		convenience function
//		First modified: lg 29/12/1999
//
//_______________________________________________________________________________________
bool CResidueGroupsInfo::GetResidueGrpsFormats(CFormatVec&	formats,
											   vector<string>& aminoAcidSets,
											   string& markers) const
{
//	formats.push_back(DefaultFormat);
//	markers += DefaultMarker;
	assert(ResGrps[0].Active == true);

	formats.push_back(ResGrps[0].Format);
	markers += ResGrps[0].Marker;
	for (unsigned i = 2; i < ResGrps.size(); ++i)
	{
		if(!ResGrps[i].Active)
			continue;
		formats.push_back(ResGrps[i].Format);

		markers += ResGrps[i].Marker;
		aminoAcidSets.push_back(ResGrps[i].Residues);
	}
	formats.push_back(ResGrps[1].Format);
	markers += ResGrps[1].Marker;
	return ResGrps[1].Active;
}


void CResidueGroupsInfo::GetResidueGrpsFormats(CFormatVec&	formats) const
{
	assert(ResGrps[0].Active == true);

	formats.push_back(ResGrps[0].Format);
	for (unsigned i = 2; i < ResGrps.size(); ++i)
	{
		if(!ResGrps[i].Active)
			continue;
		formats.push_back(ResGrps[i].Format);
	}
	formats.push_back(ResGrps[1].Format);
}


//_______________________________________________________________________________________
//
//	Stream operators
//
//		NB Caller should use exception handling
//
//		If the file is not a binary configuration file, just returns without
//			streaming anything in. (Test for ResGrps.size())
//		Everything else throws exceptions
//
//		First modified: lg 28/12/1999
//
//_______________________________________________________________________________________
ostream& operator << (ostream& os, const CResidueGroupsInfo& i)
{
	i.Write(os);
	return os;
}

CBinary_ostream& operator << (CBinary_ostream& os, const CResidueGroupsInfo& r)
{
	os << 0x5043474C;
	// write first line: fonts size and name
	os	<< r.TextSizeInTenths << r.TextFont;

	// write number of residue groups
	os	<< (unsigned)r.ResGrps.size();

	// write residue group data
	for (unsigned i = 0; i < r.ResGrps.size(); ++i)
		os	<< r.ResGrps[i].Name
			<< r.ResGrps[i].Residues
			<< r.ResGrps[i].Format.TextColour
			<< r.ResGrps[i].Format.BackColour
			<< r.ResGrps[i].Format.Bold
			<< r.ResGrps[i].Format.Italic
			<< r.ResGrps[i].Active
			<< r.ResGrps[i].Marker;
	os << 0x5043474C;
	return os;
}

CBinary_istream& operator >> (CBinary_istream& is, CResidueGroupsInfo& o)
{
	o.ErrorLineNumber = 0;
	unsigned marker;

	o.ResGrps.clear();
	is >> marker;
	if (marker != 0x5043474C)
	{
		is.setstate(std::ios_base::badbit);
		return is;
	}
	o.ErrorLineNumber = 1;

	// read first line: fonts size and name
	is	>> o.TextSizeInTenths >> o.TextFont;

	// read number of residue groups
	unsigned sz;
	is	>> sz;
	if (sz > 256)
	{
		is.setstate(std::ios_base::badbit);
		sz = 256;
	}

	o.ResGrps.reserve(sz);

	CResidueGroupInfo tmp;

	// read data lines
	for (unsigned i = 0; i < sz; ++i)
	{
		is	>> tmp.Name
			>> tmp.Residues
			>> tmp.Format.TextColour
			>> tmp.Format.BackColour
			>> tmp.Format.Bold
			>> tmp.Format.Italic
			>> tmp.Active
			>> tmp.Marker;
		o.ResGrps.push_back(tmp);
	}
	assert(sz >= 2);
	// fail save checks
	o.ResGrps[0].Name		= "Default";
	o.ResGrps[0].Residues = "X";
	o.ResGrps[0].Active	= true;
	o.ResGrps[1].Name		= "Single";
	o.ResGrps[0].Residues = "X";

	is >> marker;
	if (marker != 0x5043474C)
		is.setstate(std::ios_base::badbit);

	if (!is)
	{
		// Load failed. Clear anything because any streamed data could be corrupt
		o.ResGrps.clear();
		o.TextFont = "";
		o.TextSizeInTenths = 0;

		//"The configuration data is corrupt"
		LoadString(o.ErrorStr, IDS_ERR_RESGRP_CORRUPT_DATA);
	}

	return is;

}
